module.exports = {
  /**
   * Default way of computing shares (can be overridden using hooks).
   * Compute the shares of the secret (as many shares as parties) using Shamir secret sharing
   * @ignore
   * @function jiff_compute_shares
   * @param {module:jiff-client~JIFFClient} jiff - the jiff instance
   * @param {number} secret - the secret to share.
   * @param {Array} parties_list - array of party ids to share with.
   * @param {number} threshold - the min number of parties needed to reconstruct the secret, defaults to all the receivers.
   * @param {number} Zp - the mod.
   * @returns {object} a map between party number and its share, this means that (party number, share) is a
   *          point from the polynomial.
   *
   */
  jiff_compute_shares: function (jiff, secret, parties_list, threshold, Zp) {
    var shares = {}; // Keeps the shares
    var i;

    // Each player's random polynomial f must have
    // degree threshold - 1, so that threshold many points are needed
    // to interpolate/reconstruct.
    var t = threshold - 1;
    var polynomial = Array(t + 1); // stores the coefficients

    // Each players's random polynomial f must be constructed
    // such that f(0) = secret
    polynomial[0] = secret;

    // Compute the random polynomial f's coefficients
    for (i = 1; i <= t; i++) {
      polynomial[i] = jiff.helpers.random(Zp);
    }

    // Compute each players share such that share[i] = f(i)
    for (i = 0; i < parties_list.length; i++) {
      var p_id = parties_list[i];
      shares[p_id] = polynomial[0];
      var power = jiff.helpers.get_party_number(p_id);

      for (var j = 1; j < polynomial.length; j++) {
        var tmp = jiff.helpers.mod((polynomial[j] * power), Zp);
        shares[p_id] = jiff.helpers.mod((shares[p_id] + tmp), Zp);
        power = jiff.helpers.mod(power * jiff.helpers.get_party_number(p_id), Zp);
      }
    }

    return shares;
  },
  /**
   * Share given secret to the participating parties.
   * @ignore
   * @function jiff_share
   * @param {module:jiff-client~JIFFClient} jiff - the jiff instance
   * @param {number} secret - the secret to share.
   * @param {number} [threshold=receivers_list.length] - the min number of parties needed to reconstruct the secret, defaults to all the receivers.
   * @param {Array} [receivers_list=all_parties] - array of party ids to share with, by default, this includes all parties.
   * @param {Array} [senders_list=all_parties] - array of party ids to receive from, by default, this includes all parties.
   * @param {number} [Zp=jiff.Zp] - the mod (if null then the default Zp for the instance is used).
   * @param {string|number} [share_id=auto_gen()] - the tag used to tag the messages sent by this share operation, this tag is used
   *                                   so that parties distinguish messages belonging to this share operation from other
   *                                   share operations between the same parties (when the order of execution is not
   *                                   deterministic). An automatic id is generated by increasing a local counter, default
   *                                   ids suffice when all parties execute all sharing operations with the same senders
   *                                   and receivers in the same order.
   * @returns {object} a map where the key is the sender party id
   *          and the value is the share object that wraps
   *          what was sent from that party (the internal value maybe deferred).
   *          if the party that calls this function is not a receiver then the map
   *          will be empty.
   */
  jiff_share: function (jiff, secret, threshold, receivers_list, senders_list, Zp, share_id) {
    var i, p_id;

    // defaults
    if (Zp == null) {
      Zp = jiff.Zp;
    }
    if (receivers_list == null) {
      receivers_list = [];
      for (i = 1; i <= jiff.party_count; i++) {
        receivers_list.push(i);
      }
    } else {
      jiff.helpers.sort_ids(receivers_list);
    }
    if (senders_list == null) {
      senders_list = [];
      for (i = 1; i <= jiff.party_count; i++) {
        senders_list.push(i);
      }
    } else {
      jiff.helpers.sort_ids(senders_list);
    }
    if (threshold == null) {
      threshold = receivers_list.length;
    }
    if (threshold < 0) {
      threshold = 2;
    }
    if (threshold > receivers_list.length) {
      threshold = receivers_list.length;
    }

    // if party is uninvolved in the share, do nothing
    if (receivers_list.indexOf(jiff.id) === -1 && senders_list.indexOf(jiff.id) === -1) {
      return {};
    }

    // compute operation id
    if (share_id == null) {
      share_id = jiff.counters.gen_op_id2('share', receivers_list, senders_list);
    }

    // stage sending of shares
    if (senders_list.indexOf(jiff.id) > -1) {
      // Call hook
      secret = jiff.hooks.execute_array_hooks('beforeShare', [jiff, secret, threshold, receivers_list, senders_list, Zp], 1);

      // compute shares
      var shares = jiff.hooks.computeShares(jiff, secret, receivers_list, threshold, Zp);

      // Call hook
      shares = jiff.hooks.execute_array_hooks('afterComputeShare', [jiff, shares, threshold, receivers_list, senders_list, Zp], 1);

      // send shares
      for (i = 0; i < receivers_list.length; i++) {
        p_id = receivers_list[i];
        if (p_id === jiff.id) {
          continue;
        }

        // send encrypted and signed shares_id[p_id] to party p_id
        var msg = {party_id: p_id, share: shares[p_id], op_id: share_id};
        msg = jiff.hooks.execute_array_hooks('beforeOperation', [jiff, 'share', msg], 2);

        msg['share'] = jiff.hooks.encryptSign(jiff, msg['share'].toString(10), jiff.keymap[msg['party_id']], jiff.secret_key);
        jiff.socket.safe_emit('share', JSON.stringify(msg));
      }
    }

    // stage receiving of shares
    var result = {};
    if (receivers_list.indexOf(jiff.id) > -1) {
      // setup a map of deferred for every received share
      if (jiff.deferreds[share_id] == null) {
        jiff.deferreds[share_id] = {};
      }

      var _remaining = senders_list.length;
      for (i = 0; i < senders_list.length; i++) {
        p_id = senders_list[i];
        if (p_id === jiff.id) { // Keep party's own share
          var my_share = jiff.hooks.execute_array_hooks('receiveShare', [jiff, p_id, shares[p_id]], 2);
          result[p_id] = new jiff.SecretShare(my_share, receivers_list, threshold, Zp);
          _remaining--;
          continue;
        }

        // check if a deferred is set up (maybe the message was previously received)
        if (jiff.deferreds[share_id][p_id] == null) { // not ready, setup a deferred
          jiff.deferreds[share_id][p_id] = new jiff.helpers.Deferred();
        }

        var promise = jiff.deferreds[share_id][p_id].promise;
        // destroy deferred when done
        (function (promise, p_id) { // p_id is modified in a for loop, must do this to avoid scoping issues.
          promise.then(function () {
            delete jiff.deferreds[share_id][p_id];
            _remaining--;
            if (_remaining === 0) {
              delete jiff.deferreds[share_id];
            }
          });
        })(promise, p_id);

        // receive share_i[id] from party p_id
        result[p_id] = new jiff.SecretShare(promise, receivers_list, threshold, Zp);
      }
    }

    return result;
  }
};